/*-------------------------------------------------------------------------
 *
 * ginutil.c
 *	  Postgres 倒排索引访问方法的实用例程。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *			src/backend/access/gin/ginutil.c
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/gin_private.h"
#include "access/ginxlog.h"
#include "access/reloptions.h"
#include "access/xloginsert.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_type.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "storage/indexfsm.h"
#include "storage/lmgr.h"
#include "storage/predicate.h"
#include "utils/builtins.h"
#include "utils/index_selfuncs.h"
#include "utils/typcache.h"


/*
 * GIN 处理函数：返回带有访问方法参数和回调的 IndexAmRoutine
 */
Datum ginhandler(PG_FUNCTION_ARGS)
{
	IndexAmRoutine *fc_amroutine = makeNode(IndexAmRoutine);

	fc_amroutine->amstrategies = 0;
	fc_amroutine->amsupport = GINNProcs;
	fc_amroutine->amoptsprocnum = GIN_OPTIONS_PROC;
	fc_amroutine->amcanorder = false;
	fc_amroutine->amcanorderbyop = false;
	fc_amroutine->amcanbackward = false;
	fc_amroutine->amcanunique = false;
	fc_amroutine->amcanmulticol = true;
	fc_amroutine->amoptionalkey = true;
	fc_amroutine->amsearcharray = false;
	fc_amroutine->amsearchnulls = false;
	fc_amroutine->amstorage = true;
	fc_amroutine->amclusterable = false;
	fc_amroutine->ampredlocks = true;
	fc_amroutine->amcanparallel = false;
	fc_amroutine->amcaninclude = false;
	fc_amroutine->amusemaintenanceworkmem = true;
	fc_amroutine->amparallelvacuumoptions =
		VACUUM_OPTION_PARALLEL_BULKDEL | VACUUM_OPTION_PARALLEL_CLEANUP;
	fc_amroutine->amkeytype = InvalidOid;

	fc_amroutine->ambuild = ginbuild;
	fc_amroutine->ambuildempty = ginbuildempty;
	fc_amroutine->aminsert = gininsert;
	fc_amroutine->ambulkdelete = ginbulkdelete;
	fc_amroutine->amvacuumcleanup = ginvacuumcleanup;
	fc_amroutine->amcanreturn = NULL;
	fc_amroutine->amcostestimate = gincostestimate;
	fc_amroutine->amoptions = ginoptions;
	fc_amroutine->amproperty = NULL;
	fc_amroutine->ambuildphasename = NULL;
	fc_amroutine->amvalidate = ginvalidate;
	fc_amroutine->amadjustmembers = ginadjustmembers;
	fc_amroutine->ambeginscan = ginbeginscan;
	fc_amroutine->amrescan = ginrescan;
	fc_amroutine->amgettuple = NULL;
	fc_amroutine->amgetbitmap = gingetbitmap;
	fc_amroutine->amendscan = ginendscan;
	fc_amroutine->ammarkpos = NULL;
	fc_amroutine->amrestrpos = NULL;
	fc_amroutine->amestimateparallelscan = NULL;
	fc_amroutine->aminitparallelscan = NULL;
	fc_amroutine->amparallelrescan = NULL;

	PG_RETURN_POINTER(fc_amroutine);
}

/*
 * initGinState：填充一个空的 GinState 结构以描述索引
 *
 * 备注：各种附属数据在 CurrentMemoryContext 中分配。
 */
void initGinState(GinState *fc_state, Relation fc_index)
{
	TupleDesc	fc_origTupdesc = RelationGetDescr(fc_index);
	int			fc_i;

	MemSet(fc_state, 0, sizeof(GinState));

	fc_state->index = fc_index;
	fc_state->oneCol = (fc_origTupdesc->natts == 1);
	fc_state->origTupdesc = fc_origTupdesc;

	for (fc_i = 0; fc_i < fc_origTupdesc->natts; fc_i++)
	{
		Form_pg_attribute fc_attr = TupleDescAttr(fc_origTupdesc, fc_i);

		if (fc_state->oneCol)
			fc_state->tupdesc[fc_i] = fc_state->origTupdesc;
		else
		{
			fc_state->tupdesc[fc_i] = CreateTemplateTupleDesc(2);

			TupleDescInitEntry(fc_state->tupdesc[fc_i], (AttrNumber) 1, NULL,
							   INT2OID, -1, 0);
			TupleDescInitEntry(fc_state->tupdesc[fc_i], (AttrNumber) 2, NULL,
							   fc_attr->atttypid,
							   fc_attr->atttypmod,
							   fc_attr->attndims);
			TupleDescInitEntryCollation(fc_state->tupdesc[fc_i], (AttrNumber) 2,
										fc_attr->attcollation);
		}

		/*
		 * 如果在 opclass 定义中没有指定比较过程，则查找
		 * 索引键类型的默认 btree 比较器。
		 */
		if (index_getprocid(fc_index, fc_i + 1, GIN_COMPARE_PROC) != InvalidOid)
		{
			fmgr_info_copy(&(fc_state->compareFn[fc_i]),
						   index_getprocinfo(fc_index, fc_i + 1, GIN_COMPARE_PROC),
						   CurrentMemoryContext);
		}
		else
		{
			TypeCacheEntry *fc_typentry;

			fc_typentry = lookup_type_cache(fc_attr->atttypid,
										 TYPECACHE_CMP_PROC_FINFO);
			if (!OidIsValid(fc_typentry->cmp_proc_finfo.fn_oid))
				ereport(ERROR,
						(errcode(ERRCODE_UNDEFINED_FUNCTION),
						 errmsg("could not identify a comparison function for type %s",
								format_type_be(fc_attr->atttypid))));
			fmgr_info_copy(&(fc_state->compareFn[fc_i]),
						   &(fc_typentry->cmp_proc_finfo),
						   CurrentMemoryContext);
		}

		/* Opclass 必须始终提供提取过程 */
		fmgr_info_copy(&(fc_state->extractValueFn[fc_i]),
					   index_getprocinfo(fc_index, fc_i + 1, GIN_EXTRACTVALUE_PROC),
					   CurrentMemoryContext);
		fmgr_info_copy(&(fc_state->extractQueryFn[fc_i]),
					   index_getprocinfo(fc_index, fc_i + 1, GIN_EXTRACTQUERY_PROC),
					   CurrentMemoryContext);

		/*
		 * 检查 opclass 能否进行三态或二元逻辑一致性
		 * 检查。
		 */
		if (index_getprocid(fc_index, fc_i + 1, GIN_TRICONSISTENT_PROC) != InvalidOid)
		{
			fmgr_info_copy(&(fc_state->triConsistentFn[fc_i]),
						   index_getprocinfo(fc_index, fc_i + 1, GIN_TRICONSISTENT_PROC),
						   CurrentMemoryContext);
		}

		if (index_getprocid(fc_index, fc_i + 1, GIN_CONSISTENT_PROC) != InvalidOid)
		{
			fmgr_info_copy(&(fc_state->consistentFn[fc_i]),
						   index_getprocinfo(fc_index, fc_i + 1, GIN_CONSISTENT_PROC),
						   CurrentMemoryContext);
		}

		if (fc_state->consistentFn[fc_i].fn_oid == InvalidOid &&
			fc_state->triConsistentFn[fc_i].fn_oid == InvalidOid)
		{
			elog(ERROR, "missing GIN support function (%d or %d) for attribute %d of index \"%s\"",
				 GIN_CONSISTENT_PROC, GIN_TRICONSISTENT_PROC,
				 fc_i + 1, RelationGetRelationName(fc_index));
		}

		/*
		 * 检查 opclass 能否进行部分匹配。
		 */
		if (index_getprocid(fc_index, fc_i + 1, GIN_COMPARE_PARTIAL_PROC) != InvalidOid)
		{
			fmgr_info_copy(&(fc_state->comparePartialFn[fc_i]),
						   index_getprocinfo(fc_index, fc_i + 1, GIN_COMPARE_PARTIAL_PROC),
						   CurrentMemoryContext);
			fc_state->canPartialMatch[fc_i] = true;
		}
		else
		{
			fc_state->canPartialMatch[fc_i] = false;
		}

		/*
		 * 如果索引列有指定的排序规则，我们在进行比较时
		 * 应该尊重这一点。然而，我们可能有一个可排序的存储
		 * 类型用于非可排序的索引数据类型（例如，hstore
		 * 使用文本索引条目）。如果没有索引排序规则，那么
		 * 应指定默认排序规则，以防支持函数需要
		 * 排序规则。如果支持函数不关心
		 * 排序规则，这样做是无害的，因此我们就无条件地这么做。
		 * （我们也可以调用 get_typcollation，但那似乎是昂贵的
		 * 过度 - 不会有任何情况下 GIN 存储
		 * 类型有非默认排序规则。）
		 */
		if (OidIsValid(fc_index->rd_indcollation[fc_i]))
			fc_state->supportCollation[fc_i] = fc_index->rd_indcollation[fc_i];
		else
			fc_state->supportCollation[fc_i] = DEFAULT_COLLATION_OID;
	}
}

/*
 * 从 GIN 元组中提取存储条目的属性（列）编号
 */
OffsetNumber gintuple_get_attrnum(GinState *fc_ginstate, IndexTuple fc_tuple)
{
	OffsetNumber fc_colN;

	if (fc_ginstate->oneCol)
	{
		/* 列编号未显式存储 */
		fc_colN = FirstOffsetNumber;
	}
	else
	{
		Datum		fc_res;
		bool		fc_isnull;

		/*
		 * 第一个属性始终是 int16，因此我们可以安全地使用任何元组
		 * 描述符来获取元组的第一个属性
		 */
		fc_res = index_getattr(fc_tuple, FirstOffsetNumber, fc_ginstate->tupdesc[0],
							&fc_isnull);
		Assert(!fc_isnull);

		fc_colN = DatumGetUInt16(fc_res);
		Assert(fc_colN >= FirstOffsetNumber && fc_colN <= fc_ginstate->origTupdesc->natts);
	}

	return fc_colN;
}

/*
 * 从 GIN 元组中提取存储的数值（和可能的 null 类别）
 */
Datum gintuple_get_key(GinState *fc_ginstate, IndexTuple fc_tuple,
				 GinNullCategory *fc_category)
{
	Datum		fc_res;
	bool		fc_isnull;

	if (fc_ginstate->oneCol)
	{
		/*
		 * 单列索引不在元组中存储属性编号
		 */
		fc_res = index_getattr(fc_tuple, FirstOffsetNumber, fc_ginstate->origTupdesc,
							&fc_isnull);
	}
	else
	{
		/*
		 * 由于数值类型取决于它来自哪个索引列，
		 * 我们必须小心使用正确的元组描述符。
		 */
		OffsetNumber fc_colN = gintuple_get_attrnum(fc_ginstate, fc_tuple);

		fc_res = index_getattr(fc_tuple, OffsetNumberNext(FirstOffsetNumber),
							fc_ginstate->tupdesc[fc_colN - 1],
							&fc_isnull);
	}

	if (fc_isnull)
		*fc_category = GinGetNullCategory(fc_tuple, fc_ginstate);
	else
		*fc_category = GIN_CAT_NORM_KEY;

	return fc_res;
}

/*
 * 分配一个新页面（通过回收或扩展索引文件）
 * 返回的缓冲区已经被固定和独占锁定
 * 调用者负责通过调用 GinInitBuffer 初始化页面
 */
Buffer GinNewBuffer(Relation fc_index)
{
	Buffer		fc_buffer;
	bool		fc_needLock;

	/* 首先，尝试从 FSM 获取一个页面 */
	for (;;)
	{
		BlockNumber fc_blkno = GetFreeIndexPage(fc_index);

		if (fc_blkno == InvalidBlockNumber)
			break;

		fc_buffer = ReadBuffer(fc_index, fc_blkno);

		/*
		 * 我们必须防止其他人已经回收此页面的可能性；
		 * 如果这样，缓冲区可能会被锁定。
		 */
		if (ConditionalLockBuffer(fc_buffer))
		{
			if (GinPageIsRecyclable(BufferGetPage(fc_buffer)))
				return fc_buffer;	/* 可以使用 */

			LockBuffer(fc_buffer, GIN_UNLOCK);
		}

		/* 不能使用，因此释放缓冲区并重试 */
		ReleaseBuffer(fc_buffer);
	}

	/* 必须扩展文件 */
	fc_needLock = !RELATION_IS_LOCAL(fc_index);
	if (fc_needLock)
		LockRelationForExtension(fc_index, ExclusiveLock);

	fc_buffer = ReadBuffer(fc_index, P_NEW);
	LockBuffer(fc_buffer, GIN_EXCLUSIVE);

	if (fc_needLock)
		UnlockRelationForExtension(fc_index, ExclusiveLock);

	return fc_buffer;
}

void GinInitPage(Page fc_page, uint32 fc_f, Size fc_pageSize)
{
	GinPageOpaque fc_opaque;

	PageInit(fc_page, fc_pageSize, sizeof(GinPageOpaqueData));

	fc_opaque = GinPageGetOpaque(fc_page);
	fc_opaque->flags = fc_f;
	fc_opaque->rightlink = InvalidBlockNumber;
}

void GinInitBuffer(Buffer fc_b, uint32 fc_f)
{
	GinInitPage(BufferGetPage(fc_b), fc_f, BufferGetPageSize(fc_b));
}

void GinInitMetabuffer(Buffer fc_b)
{
	GinMetaPageData *fc_metadata;
	Page		fc_page = BufferGetPage(fc_b);

	GinInitPage(fc_page, GIN_META, BufferGetPageSize(fc_b));

	fc_metadata = GinPageGetMeta(fc_page);

	fc_metadata->head = fc_metadata->tail = InvalidBlockNumber;
	fc_metadata->tailFreeSize = 0;
	fc_metadata->nPendingPages = 0;
	fc_metadata->nPendingHeapTuples = 0;
	fc_metadata->nTotalPages = 0;
	fc_metadata->nEntryPages = 0;
	fc_metadata->nDataPages = 0;
	fc_metadata->nEntries = 0;
	fc_metadata->ginVersion = GIN_CURRENT_VERSION;

	/*
	 * 将 pd_lower 设置在元数据结束的后面。这是至关重要的，因为不这样做，如果 xlog.c 压缩页面，元数据将丢失。
	 */
	((PageHeader) fc_page)->pd_lower =
		((char *) fc_metadata + sizeof(GinMetaPageData)) - (char *) fc_page;
}

/*
 * 比较同一索引列的两个键
 */
int ginCompareEntries(GinState *fc_ginstate, OffsetNumber fc_attnum,
				  Datum fc_a, GinNullCategory fc_categorya,
				  Datum fc_b, GinNullCategory fc_categoryb)
{
	/* 如果不属于同一 null 类别，先按此排序 */
	if (fc_categorya != fc_categoryb)
		return (fc_categorya < fc_categoryb) ? -1 : 1;

	/* 所有 null 项目在同一类别中相等 */
	if (fc_categorya != GIN_CAT_NORM_KEY)
		return 0;

	/* 两者都不为 null，因此可以安全地调用 compareFn */
	return DatumGetInt32(FunctionCall2Coll(&fc_ginstate->compareFn[fc_attnum - 1],
										   fc_ginstate->supportCollation[fc_attnum - 1],
										   fc_a, fc_b));
}

/*
 * 比较可能不同索引列的两个键
 */
int ginCompareAttEntries(GinState *fc_ginstate,
					 OffsetNumber fc_attnuma, Datum fc_a, GinNullCategory fc_categorya,
					 OffsetNumber fc_attnumb, Datum fc_b, GinNullCategory fc_categoryb)
{
	/* 属性编号是第一个排序关键字 */
	if (fc_attnuma != fc_attnumb)
		return (fc_attnuma < fc_attnumb) ? -1 : 1;

	return ginCompareEntries(fc_ginstate, fc_attnuma, fc_a, fc_categorya, fc_b, fc_categoryb);
}


/*
 * 支持在 ginExtractEntries 中对键数据进行排序
 *
 * 注意：我们只需关注这里的 null 和非 null 键；
 * ginExtractEntries 从不生成超过一个占位符 null，
 * 所以不必对它们进行排序。
 */
typedef struct
{
	Datum		datum;
	bool		isnull;
} keyEntryData;

typedef struct
{
	FmgrInfo   *cmpDatumFunc;
	Oid			collation;
	bool		haveDups;
} cmpEntriesArg;

static int fc_cmpEntries(const void *fc_a, const void *fc_b, void *fc_arg)
{
	const keyEntryData *fc_aa = (const keyEntryData *) fc_a;
	const keyEntryData *fc_bb = (const keyEntryData *) fc_b;
	cmpEntriesArg *fc_data = (cmpEntriesArg *) fc_arg;
	int			fc_res;

	if (fc_aa->isnull)
	{
		if (fc_bb->isnull)
			fc_res = 0;			/* NULL "=" NULL */
		else
			fc_res = 1;			/* NULL ">" 非 NULL */
	}
	else if (fc_bb->isnull)
		fc_res = -1;				/* 非 NULL "<" NULL */
	else
		fc_res = DatumGetInt32(FunctionCall2Coll(fc_data->cmpDatumFunc,
											  fc_data->collation,
											  fc_aa->datum, fc_bb->datum));

	/*
	 * 检测我们是否有任何重复项。如果有相等的键，qsort 必须
	 * 在某个时刻进行比较，否则它将不知道哪个应该在前或在后。
	 */
	if (fc_res == 0)
		fc_data->haveDups = true;

	return fc_res;
}


/*
 * 从可索引项中提取索引键值
 *
 * 生成的键值经过排序，任何重复项都被删除。
 * 这避免了生成冗余的索引条目。
 */
Datum * ginExtractEntries(GinState *fc_ginstate, OffsetNumber fc_attnum,
				  Datum fc_value, bool fc_isNull,
				  int32 *fc_nentries, GinNullCategory **fc_categories)
{
	Datum	   *fc_entries;
	bool	   *fc_nullFlags;
	int32		fc_i;

	/*
	 * 我们不对空项调用 extractValueFn。相反，生成一个
	 * 占位符。
	 */
	if (fc_isNull)
	{
		*fc_nentries = 1;
		fc_entries = (Datum *) palloc(sizeof(Datum));
		fc_entries[0] = (Datum) 0;
		*fc_categories = (GinNullCategory *) palloc(sizeof(GinNullCategory));
		(*fc_categories)[0] = GIN_CAT_NULL_ITEM;
		return fc_entries;
	}

	/* 好的，调用操作类的 extractValueFn */
	fc_nullFlags = NULL;			/* 以防 extractValue 没有设置它 */
	fc_entries = (Datum *)
		DatumGetPointer(FunctionCall3Coll(&fc_ginstate->extractValueFn[fc_attnum - 1],
										  fc_ginstate->supportCollation[fc_attnum - 1],
										  fc_value,
										  PointerGetDatum(fc_nentries),
										  PointerGetDatum(&fc_nullFlags)));

	/*
	 * 如果项中没有键，则生成一个占位符。
	 */
	if (fc_entries == NULL || *fc_nentries <= 0)
	{
		*fc_nentries = 1;
		fc_entries = (Datum *) palloc(sizeof(Datum));
		fc_entries[0] = (Datum) 0;
		*fc_categories = (GinNullCategory *) palloc(sizeof(GinNullCategory));
		(*fc_categories)[0] = GIN_CAT_EMPTY_ITEM;
		return fc_entries;
	}

	/*
	 * 如果 extractValueFn 没有创建 nullFlags 数组，则创建一个，
	 * 假设所有内容都是非 null。
	 */
	if (fc_nullFlags == NULL)
		fc_nullFlags = (bool *) palloc0(*fc_nentries * sizeof(bool));

	/*
	 * 如果有超过一个键，则进行排序和去重。
	 *
	 * XXX 在这里使用 qsort 在符号上是痛苦的，开销也相当大。
	 * 对于少量键，使用简单的插入排序可能更好。
	 */
	if (*fc_nentries > 1)
	{
		keyEntryData *fc_keydata;
		cmpEntriesArg fc_arg;

		fc_keydata = (keyEntryData *) palloc(*fc_nentries * sizeof(keyEntryData));
		for (fc_i = 0; fc_i < *fc_nentries; fc_i++)
		{
			fc_keydata[fc_i].datum = fc_entries[fc_i];
			fc_keydata[fc_i].isnull = fc_nullFlags[fc_i];
		}

		fc_arg.cmpDatumFunc = &fc_ginstate->compareFn[fc_attnum - 1];
		fc_arg.collation = fc_ginstate->supportCollation[fc_attnum - 1];
		fc_arg.haveDups = false;
		qsort_arg(fc_keydata, *fc_nentries, sizeof(keyEntryData),
				  fc_cmpEntries, (void *) &fc_arg);

		if (fc_arg.haveDups)
		{
			/* 存在重复项，必须去掉它们 */
			int32		fc_j;

			fc_entries[0] = fc_keydata[0].datum;
			fc_nullFlags[0] = fc_keydata[0].isnull;
			fc_j = 1;
			for (fc_i = 1; fc_i < *fc_nentries; fc_i++)
			{
				if (fc_cmpEntries(&fc_keydata[fc_i - 1], &fc_keydata[fc_i], &fc_arg) != 0)
				{
					fc_entries[fc_j] = fc_keydata[fc_i].datum;
					fc_nullFlags[fc_j] = fc_keydata[fc_i].isnull;
					fc_j++;
				}
			}
			*fc_nentries = fc_j;
		}
		else
		{
			/* 简单，没有重复项 */
			for (fc_i = 0; fc_i < *fc_nentries; fc_i++)
			{
				fc_entries[fc_i] = fc_keydata[fc_i].datum;
				fc_nullFlags[fc_i] = fc_keydata[fc_i].isnull;
			}
		}

		pfree(fc_keydata);
	}

	/*
	 * 从 nullFlags 创建 GinNullCategory 表示。
	 */
	*fc_categories = (GinNullCategory *) palloc0(*fc_nentries * sizeof(GinNullCategory));
	for (fc_i = 0; fc_i < *fc_nentries; fc_i++)
		(*fc_categories)[fc_i] = (fc_nullFlags[fc_i] ? GIN_CAT_NULL_KEY : GIN_CAT_NORM_KEY);

	return fc_entries;
}

bytea * ginoptions(Datum fc_reloptions, bool fc_validate)
{
	static const relopt_parse_elt fc_tab[] = {
		{"fastupdate", RELOPT_TYPE_BOOL, offsetof(GinOptions, useFastUpdate)},
		{"gin_pending_list_limit", RELOPT_TYPE_INT, offsetof(GinOptions,
															 pendingListCleanupSize)}
	};

	return (bytea *) build_reloptions(fc_reloptions, fc_validate,
									  RELOPT_KIND_GIN,
									  sizeof(GinOptions),
									  fc_tab, lengthof(fc_tab));
}

/*
 * 将索引的统计数据提取到 *stats 中
 *
 * 注意：在结果中，nPendingPages 可以信赖为最新，
 * ginVersion 也是；但其他字段是根据上次 VACUUM 的结果。
 */
void ginGetStats(Relation fc_index, GinStatsData *fc_stats)
{
	Buffer		fc_metabuffer;
	Page		fc_metapage;
	GinMetaPageData *fc_metadata;

	fc_metabuffer = ReadBuffer(fc_index, GIN_METAPAGE_BLKNO);
	LockBuffer(fc_metabuffer, GIN_SHARE);
	fc_metapage = BufferGetPage(fc_metabuffer);
	fc_metadata = GinPageGetMeta(fc_metapage);

	fc_stats->nPendingPages = fc_metadata->nPendingPages;
	fc_stats->nTotalPages = fc_metadata->nTotalPages;
	fc_stats->nEntryPages = fc_metadata->nEntryPages;
	fc_stats->nDataPages = fc_metadata->nDataPages;
	fc_stats->nEntries = fc_metadata->nEntries;
	fc_stats->ginVersion = fc_metadata->ginVersion;

	UnlockReleaseBuffer(fc_metabuffer);
}

/*
 * 将给定的统计信息写入索引的元页面
 *
 * 注意：nPendingPages 和 ginVersion *不* 会被复制
 */
void ginUpdateStats(Relation fc_index, const GinStatsData *fc_stats, bool fc_is_build)
{
	Buffer		fc_metabuffer;
	Page		fc_metapage;
	GinMetaPageData *fc_metadata;

	fc_metabuffer = ReadBuffer(fc_index, GIN_METAPAGE_BLKNO);
	LockBuffer(fc_metabuffer, GIN_EXCLUSIVE);
	fc_metapage = BufferGetPage(fc_metabuffer);
	fc_metadata = GinPageGetMeta(fc_metapage);

	START_CRIT_SECTION();

	fc_metadata->nTotalPages = fc_stats->nTotalPages;
	fc_metadata->nEntryPages = fc_stats->nEntryPages;
	fc_metadata->nDataPages = fc_stats->nDataPages;
	fc_metadata->nEntries = fc_stats->nEntries;

	/*
	 * 将 pd_lower 设置在元数据的末尾之后。 这是必不可少的，因为如果不这样做，如果 xlog.c 压缩页面，元数据将丢失。 （我们必须在这里做这件事，因为早于 v11 的 PG 版本没有正确设置元页面的 pd_lower，因此 pg_upgraded 索引可能包含错误的值。）
	 */
	((PageHeader) fc_metapage)->pd_lower =
		((char *) fc_metadata + sizeof(GinMetaPageData)) - (char *) fc_metapage;

	MarkBufferDirty(fc_metabuffer);

	if (RelationNeedsWAL(fc_index) && !fc_is_build)
	{
		XLogRecPtr	fc_recptr;
		ginxlogUpdateMeta fc_data;

		fc_data.node = fc_index->rd_node;
		fc_data.ntuples = 0;
		fc_data.newRightlink = fc_data.prevTail = InvalidBlockNumber;
		memcpy(&fc_data.metadata, fc_metadata, sizeof(GinMetaPageData));

		XLogBeginInsert();
		XLogRegisterData((char *) &fc_data, sizeof(ginxlogUpdateMeta));
		XLogRegisterBuffer(0, fc_metabuffer, REGBUF_WILL_INIT | REGBUF_STANDARD);

		fc_recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_UPDATE_META_PAGE);
		PageSetLSN(fc_metapage, fc_recptr);
	}

	UnlockReleaseBuffer(fc_metabuffer);

	END_CRIT_SECTION();
}
